#ifndef _NE_BILLBOARD_SET_H_
#define _NE_BILLBOARD_SET_H_

#include <graphics/Billboard.h>
#include <graphics/GLBufferObjectManager.h>
#include <graphics/VertexData.h>
#include <graphics/IndexData.h>
#include <graphics/Camera.h>
#include <algorithm/RadixSort32.h>

namespace ne
{
    typedef TransformAlignType BillboardOrigin;

    enum BillboardType
    {
        // Standard point billboard (default), always faces the camera completely and is always upright
        BBT_POINT,
        // Billboards are oriented around a shared axis vector (BillboardSet's Y axis) and only rotate around this to face the camera
        /* 1-------3
           |   |   |
           |   |   |
           |   |   |
           0-------2
        */
        BBT_ORIENTED_COMMON,
        // Billboards are oriented around their own axis vector (their own Y axis) and only rotate around this to face the camera
        BBT_ORIENTED_SELF,
        // Billboards are perpendicular to a shared axis vector (BillboardSet's Y axis, the facing direction) and X, Y axis are determined by a shared up-vertor
        /* 1-------3
           |       |
           |   .   |
           |       |
           0-------2
        */
        BBT_PERPENDICULAR_COMMON,
        // Billboards are perpendicular to their own axis vector (their own Y axis, the facing direction) and X, Y axis are determined by a shared up-vertor
        BBT_PERPENDICULAR_SELF
    };

    class NEAPI BillboardSet : public Node
    {
        public:
            BillboardSet();

            void setBillboardType(const BillboardType bbt);
            BillboardType getBillboardType() const;

            void setOriginType(const BillboardOrigin bbo);
            BillboardOrigin getOriginType() const;

            void setSize(const real width, const real height);
            void setWidth(const real width);
            real getWidth() const;
            void setHeight(const real height);
            real getHeight() const;

            void setPointRendering(const bool b);
            bool isPointRendering() const;

            void setAccurateFacing(const bool b);
            bool isAccurateFacing() const;

            void setUseOwnSize(const bool b);
            bool isUseOwnSize() const;

            void setSortEnable(const bool b);
            bool isSortEnable() const;

            SortMode getSortMode() const;

            void setCullIndividually(const bool b);
            bool isCullIndividually() const;

            void setUseColor(const bool b);
            bool isUseColor() const;

            void setUseTexCoord(const bool b);
            bool isUseTexCoord() const;

            size_t getVisibleBillboardNum() const;

            void setBillboardPoolSize(const size_t size);

            size_t getBillboardPoolSize() const;

            Billboard* createBillboard();

            size_t getBillboardNum() const;

            Billboard* getBillboard(const size_t index);

            BillboardList& getBillboardList();

            void clearBillboards();

            GLBufferObjectPtr getVertexBuffer() const;

            GLBufferObjectPtr getIndexBuffer() const;

            VertexDataPtr getVertexData() const;

            IndexDataPtr getIndexData() const;

            void beginBillboards(const size_t numBillboards=0);

            void injectBillboard(const Billboard &bb);

            void endBillboards();

            void updateBillboards(const Camera *pCamera);

            bool isBillboardVisible(const Camera *pCamera, const Billboard &bb);

            virtual ~BillboardSet();

        protected:
            void _increaseBillboardPool(const size_t size);

            void _destroyBillboardPool();

            void _createBuffer();

            void _destroyBuffer();

            void _sortBillboards(const Camera *pCamera);

            void _genOriginOffsets(Vector4 &offsets);

            void _genBillboardAxes(Vector3 &xAxis, Vector3 &yAxis, const Billboard *bb=0);

            void _genVertexOffsets(const real width, const real height,
                                   const Vector4 &originOffsets,
                                   const Vector3 &xAxis,
                                   const Vector3 &yAxis,
                                   Vector3 *vertexOffsets);

            void _genVertices(const Vector3* const vertexOffsets, const Billboard &bb);

        protected:
            /** Sort by distance functor */
            struct SortByDistanceFunctor
            {
                /// Position to sort in
                Vector3 mSortPos;

                SortByDistanceFunctor(const Vector3& pos) : mSortPos(pos) {}
                float operator () (Billboard* bb) const
                {
                    return -(mSortPos - bb->position).squaredLength();
                }
            };

            /** Sort by direction functor */
            struct SortByDirectionFunctor
            {
                /// Direction to sort in
                Vector3 mSortDir;

                SortByDirectionFunctor(const Vector3& dir) : mSortDir(dir) {}
                float operator () (Billboard* bb) const
                {
                    return mSortDir.dotProduct(bb->position);
                }
            };

        protected:
            static RadixSort32<BillboardList, Billboard*, float> RadixSorter;

        protected:
            BillboardType mBillboardType;
            BillboardOrigin mOriginType;

            real mWidth;
            real mHeight;

            bool mPointRendering;
            bool mAccurateFacing;
            bool mUseOwnSize;
            bool mSortEnable;
            bool mCullIndividual;
            bool mUseColor;
            bool mUseTexCoord;

            BillboardVector mBillboardPool;
            BillboardList mActiveBillboards;
            BillboardList mFreeBillboards;

            GLBufferObjectPtr mpVertexBuffer;
            GLBufferObjectPtr mpIndexBuffer;
            VertexDataPtr mpVertexData;
            IndexDataPtr mpIndexData;
            real *mpVertexBufferIterator;
            uint32_t *mpIndexBufferIterator;
            size_t mActiveBillboardNum;
            size_t mVisibleBillboardNum;

            Vector3 mCurrentCameraPosition;
            Quaternion mCurrentCameraOrientation;

            // Left:x  Bottom:y  Right:z  Top:w
            Vector4 mOriginOffsets;
            Vector3 mBillboardAxes[2];
            Vector3 mVertexOffsets[4];
    };
}
#endif
